//-----------------------------------------------------------------------------
// cityGenerator.cpp
//-----------------------------------------------------------------------------

#include "AReVi/arClass.h"
#include "AReVi/arSystem.h"
#include "AReVi/Lib3D/object3D.h"
#include "AReVi/Lib3D/light3D.h"
#include "AReVi/Lib3D/boundingBox3D.h"
#include "AReVi/Lib3D/transform3D.h"
#include "AReVi/Lib3D/transform2D.h"
#include "AReVi/Lib3D/material3D.h"
#include "AReVi/Lib3D/urlTexture.h"
#include "AReVi/Shapes/shape3D.h"
#include "AReVi/Shapes/box3D.h"
#include "AReVi/Shapes/mesh3D.h"
#include "AReVi/VRML/vrmlShape3D.h"
#include "AReVi/Utils/abstractStream.h"

using namespace AReVi;

#include "city.h"


AR_CLASS_DEF(Square,ArObject)

//----------------------------------------------------------------------------

Square::Square(ArCW & arCW):
ArObject(arCW)
{
  pos[0] = 0.0; // X
  pos[1] = 0.0; // Y
  size[0] = 0.0; // X
  size[1] = 0.0; // Y
}

Square::Square(ArCW & arCW, double posX,
                            double posY,
                            double sizeX,
                            double sizeY):
ArObject(arCW){
  pos[0] = posX;
  pos[1] = posY;
  size[0] = sizeX;
  size[1]= sizeY;
}

Square::~Square(void) {}

//----------------------------------------------------------------------------

pair<ArRef<Square>, ArRef<Square> >
Square::divide(double widthBetween, double minWidth) {
  pair<ArRef<Square>, ArRef<Square> > result;

  // random draw between x and y division
  int xy = ArSystem::integerRand(2);

  if(size[xy] < 2.0*minWidth+widthBetween || size[xy]/size[1-xy] < 0.9) {
    result.first = Square::nullRef();
    result.second = Square::nullRef();
  } else {
    double roadCoord = minWidth+0.5*widthBetween +
      ArSystem::realRand()*(size[xy] - (2.0*minWidth + widthBetween));

    double firstSize[2];
    double firstPos[2];

    double secondSize[2];
    double secondPos[2];

    // size don't change on the other coordinate
    firstSize[1-xy] = size[1-xy];
    secondSize[1-xy] = size[1-xy];

    firstSize[xy] = roadCoord - 0.5 * widthBetween;
    secondSize[xy] = size[xy]-roadCoord - 0.5 * widthBetween;

    // pos don't change on the other coordinate
    firstPos[1-xy] = pos[1-xy];
    secondPos[1-xy] = pos[1-xy];

    firstPos[xy] = pos[xy] - (size[xy]/2.0) + firstSize[xy] / 2.0;
    secondPos[xy] = pos[xy] - (size[xy]/2.0) + firstSize[xy] +
      widthBetween + secondSize[xy] / 2.0 ;

    ArRef<Square> first = Square::NEW(firstPos[0],firstPos[1],firstSize[0],firstSize[1]);
    ArRef<Square> second = Square::NEW(secondPos[0], secondPos[1], secondSize[0], secondSize[1]);

    result = make_pair<ArRef<Square>, ArRef<Square> >(first,second);

  }

  return result;
}




//----------------------------------------------------------------------------

AR_CLASS_DEF(City,Element)

//----------------------------------------------------------------------------

City::City(ArCW & arCW):
Element(arCW)
{
}

City::~City(){}


void City::init() {
}

//----------------------------------------------------------------------------

void City::writeConfiguration(ArRef<AbstractOStream> stream) {
  stream->writeDouble(_sizeX);
  stream->writeDouble(_sizeY);
}
void City::readConfiguration(ArRef<AbstractIStream> stream) {
  stream->readDouble(_sizeX);
  stream->readDouble(_sizeY);


  _road = Object3D::NEW();
  ArRef<Shape3D> sh = Shape3D::NEW();
  ArRef<Box3D> mesh = Box3D::NEW();
  ArRef<Transform2D> tr = Transform2D::NEW();
  tr->preScale(_sizeX/4.0,_sizeY/4.0);
  mesh->writeTextureTransformation(tr);
  mesh->setTexture(URLTexture::NEW("models/textures/road.jpg",true,true));
  mesh->setSize(_sizeX,_sizeY,0.1);
  sh->addRootPart(mesh);
  _road->setShape(sh);
}



void City::writeUpdate(ArRef<AbstractOStream>) {
}

void City::readUpdate(ArRef<AbstractIStream>) {
}

//----------------------------------------------------------------------------

bool
City::buildCity(double sizeX,
                         double sizeY,
                         double maxSlenderness,
                         double roadwidth,
                         double sideWalkwidth,
                         double buildingMinWidth,
                         unsigned int nbbuildings) {
  StlVector<ArRef<Square> > squares;
  _sizeX = sizeX;
  _sizeY = sizeY;

  ArRef<Square> base = Square::NEW(0.0,0.0,_sizeX,_sizeY);
  squares.push_back(base);

  for(unsigned int i=1; i< nbbuildings ; i++){

    // draw a random square
    StlVector<ArRef<Square> >::iterator randSquareIt;

    if(squares.size() == 0 ){
      cerr << "ERROR : no more squares to divide !" << endl;
      return false;
    }
    else if(squares.size() > 1){
      randSquareIt = squares.begin()+ArSystem::integerRand(squares.size()-1);
    }
    else{
      randSquareIt = squares.begin();
    }

    // divide this square
    pair<ArRef<Square>, ArRef<Square> > newSquares =
      (*randSquareIt)->divide(roadwidth+2.0*sideWalkwidth,buildingMinWidth);

    if(newSquares.first) { // divide success
      squares.erase(randSquareIt);

      squares.push_back(newSquares.first);
      squares.push_back(newSquares.second);
    }
    else
      i -- ; // same player try again ;)

  }

  _computeBuildings(squares, maxSlenderness, sideWalkwidth);

  _road = Object3D::NEW();
  ArRef<Shape3D> sh = Shape3D::NEW();
  ArRef<Box3D> mesh = Box3D::NEW();
  ArRef<Transform2D> tr = Transform2D::NEW();
  tr->preScale(_sizeX/4.0,_sizeY/4.0);
  mesh->writeTextureTransformation(tr);
  mesh->setTexture(URLTexture::NEW("models/textures/road.jpg",true,true));
  mesh->setSize(_sizeX,_sizeY,0.1);
  sh->addRootPart(mesh);
  _road->setShape(sh);

  return true;
}

//----------------------------------------------------------------------------

void
City::_computeBuildings(StlVector<ArRef<Square> > squares,
                                 double maxSlenderness,
                                 double sideWalkWidth) {
  for(unsigned int i =0 ; i < squares.size() ; i++) {
    ArRef<Object3D> obj = Object3D::NEW();
    ArRef<Building> sh = Building::NEW();
    double H = sqrt(squares[i]->size[0]*squares[i]->size[1]) / maxSlenderness;

    if(H > 2.0*fmax( squares[i]->size[0], squares[i]->size[1])) 
    	H = 2.0*fmax( squares[i]->size[0], squares[i]->size[1]);

    sh->setSize(squares[i]->size[0],squares[i]->size[1],H);
    sh->setSideWalkGeometry(sideWalkWidth,0.5);
    sh->setFacadeHeight(15.0);

//     obj->setShape(sh);
    sh->setPosition(squares[i]->pos[0],squares[i]->pos[1],0.0);
    sh->init();

    _buildings.push_back(sh);
  }

}

//----------------------------------------------------------------------------

const StlVector<ArRef<Element> >&
City::getBuildings(void) {
 return _buildings;
}

//----------------------------------------------------------------------------

ArRef<Object3D>
City::getRoad(void) {
 return _road;
}
